#!/usr/bin/ruby
# Generates a template for exploit_config.h
# This script is heaviliy adapted for VHBL: it assumes that perfect syscalls are not available, and that hbl needs to be run in a "flat" folder
#
# Required input files are:
#
# 1) "memdump.bin", a user memory dump from PSPLink with 
# "savemem 0x08800000 0x01800000 memdump.bin"
#
# 2) from the same game session as the memdump: "uidlist.txt", which is the the output of the "uidlist"
# command in PSPLink
#
# 3) "sdk.S", from your exploit
#
# Initial code by JJS & wololo

max_nids = 64
call_address = 0x10000

# Copied from eLoaderconf.rb/stubs.rb/freemem.rb

class String
# Thanks to mon_Ouie for ruby 1.9 compatibility
	def to_a
		[self]
	end

	def hex_to_bin
		return gsub(/\s/,'').to_a.pack("H*")
	end
end

class Integer
	def mips(bytes)
		return to_s(16).rjust(bytes * 2, "0").hex_to_bin.reverse
	end

	def to_hex
		return to_s(16).rjust(2, "0").upcase
	end
end

def help()
puts %Q{
There are 2 modes to this tool:
* mode1 is used the first time you port HBL to a given exploit.
  you need to provide 3 files: memdump.bin, uidlist.txt, and sdk.S
  - memdump.bin: a user memory dump from PSPLink
       ("savemem 0x08800000 0x01800000 memdump.bin")
  - from the same game session as the memdump: "uidlist.txt",
       which is the the output of the "uidlist" command in PSPLink
  - sdk.S: the sdk from your exploit

* mode2 is used later on, when you don't have the memory dump available,
     but already have an existing port of the game
  mode 2 still requires sdk.S, the sdk from the exploit.
  mode2 takes a parameter: the codename of the game (e.g. lifejp)
  mode2's output is incomplete, so you need to manually
  grab the results and merge it with the existing exploit!!!
}
end

$gameName = nil;

if (ARGV.length > 0) then
	if (ARGV[0] == "-h") then
		help();
		die();
	end
	$gameName = ARGV[0];
end

# .lib.stub addresses
exploit_stubs_map = {
=begin
	# Project Life - KR Version (wololo, exploit by Teck4)
	"lifekr" =>
		[
			0x08A5A0DC, #game main module
			0x08ACCC30, #sceLibrary
			0x088009A0, #sceLibrary
			0x08AD3950, #sceLibrary
			0x08ADCA30, #sceLibrary
			0x08AE3A30, #sceLibrary
 		],

	# Project Life - US Version (wololo, exploit by Teck4)
	"lifeus" => 
		[
			0x08A5B328, #game main module
			0x08ACDC30, #sceLibrary
			0x088009A0, #sceLibrary
			0x08AD4950, #sceLibrary
			0x08ADDA30, #sceLibrary
			0x08AE4A30, #sceLibrary
 		],
	# Project Life - EU Version (wololo)
	"lifeeu" => 
		[
			0x08A5B940, #game main module
			0x08ACDC30, #sceLibrary
			0x088009A0, #sceLibrary
			0x08AD4950, #sceLibrary
			0x08ADDA30, #sceLibrary
			0x08AE4A30, #sceLibrary
		],

	# Project Life - JP Version (wololo)
	"lifejp" => 
		[
			0x08A5A280, #game main module
			0x08ACCC30, #sceLibrary
			0x088009A0, #sceLibrary
			0x08AD3950, #sceLibrary
			0x08ADCA30, #scelibrary
			0x08AE3A30, #scelibrary				
		],

	# Everybody's Tennis - EU Version (yosh)
	"tenniseu" =>
		[
			0x088009A0, #sceKernelLibrary
			0x08ABBF5C, #main
			0x09E94C30, #sceATRAC3plus_Library
			0x09EA08F0, #sceFont_Library
			0x09ED54A0, #scePsmfP_library
			0x09ED8770, #scePsmf_library
		],

	"tennisus" => 
		[
			0x088009A0, #sceKernelLibrary
			0x08ABBF1C, #main
			0x09E94C30, #sceATRAC3plus_Library
			0x09EA08F0, #sceFont_Library
			0x09ED54A0, #scePsmfP_library
			0x09ED8770, #scePsmf_library
		],
	"tennisjp" =>
		[
			0x088009A0, #sceKernelLibrary
			0x08AA8878, #main
			0x09E8FC30, #sceATRAC3plus_Library
			0x09E9B8F0, #sceFont_Library
			0x09ED0490, #scePsmfP_library
			0x09ED3720, #scePsmf_library
		],
	"tennishk" => 
		[
			0x088009A0, #sceKernelLibrary
			0x08ABC27C, #main
			0x09EA1C30, #sceATRAC3plus_Library
			0x09EAD8F0, #sceFont_Library
			0x09EE24A0, #scePsmfP_library
			0x09EE5770, #scePsmf_library
			0x09EED430, #sceMpeg_library
		],
=end
}


stubLibraries = [];
stubLibrariesNames = [];

addressOutput = {
	"TH_ADDR_LIST"=>[],
	"EV_ADDR_LIST"=>[],
	"SEMA_ADDR_LIST"=>[],
	"VPL_ADDR_LIST"=>[],
	"FPL_ADDR_LIST"=>[],
	"GAME_FREEMEM_ADDR"=>[],
}

# If a $gameName is not provided, we assume we need to do the work from
# scratch. This means we need to provide a memory dump and uidlist
if ($gameName) then
	puts("Game name is provided, output will be partial");
	stubLibraries = exploit_stubs_map[$gameName];
	if (!stubLibraries) then
		puts("game code invalid");
		help();
		die();
	end
	addressOutput = {
		"TH_ADDR_LIST"=>["COPY VALUES FROM THE EXISTING exploit_config.h"],
		"EV_ADDR_LIST"=>["COPY VALUES FROM THE EXISTING exploit_config.h"],
		"SEMA_ADDR_LIST"=>["COPY VALUES FROM THE EXISTING exploit_config.h"],
		"VPL_ADDR_LIST"=>[],
		"FPL_ADDR_LIST"=>[],
		"GAME_FREEMEM_ADDR"=>["COPY VALUES FROM THE EXISTING exploit_config.h"],
	}
else
	fileEntry = Struct.new(:type, :uid, :name)
	addresses = Array.new
	currentHeader = nil
	baseAddress = 0x08800000

	if (!File.exists?("uidlist.txt") || !File.exists?("memdump.bin")) then
		help();
		die();
	end
	uidlist = File.new("uidlist.txt", "r")
	memdumpfile = File.new("memdump.bin", "r")
	memdumpfile.binmode
	memdump = memdumpfile.read


	#
	# Find Semaphore / Thread / evlist / game addresses
	#

	#[Fpl]    UID 0x00288E0D (attr 0x0 entry 0x88014470)
	regexpHeader = /^\[(.+)\]    UID (0x.+) \(attr (0x.+) entry (0x.+)\)$/

	#(UID): 0x048E3761, (entry): 0x882471b8, (size): 48, (attr): 0xFF, (Name): sgx-ao-evf
	regexpEntry = /^\(UID\): (0x.+), \(entry\): (0x.+), \(size\): (.+), \(attr\): (0x.+), \(Name\): (.+)$/

	while (line = uidlist.gets)
		line.chomp!
		if (line.match(regexpHeader))
			line.scan(regexpHeader) { |name, uid, attr, entry|
				currentHeader = name
			}
		end

		if (line.match(regexpEntry)) 
			line.scan(regexpEntry) { |uid, entry, size, attr, name|
				if attr == "0xFF"
					# skip thread stacks and the "main" thread which is unloaded with the module
					if (name.index("stack:") == nil) and not ((currentHeader == "Thread") and (name == "main"))
						addresses.push(fileEntry.new(currentHeader, uid, name))
					end
				end
			}
		end
	end

	currentHeader = nil

	addresses.each { |addressEntry|
		if addressEntry.type == "Thread"
			# the thread uid is right next to the thread stack
			index = memdump.index(addressEntry.uid.to_i(16).mips(4) + "FF".to_i(16).mips(1))
		else
			index = memdump.index(addressEntry.uid.to_i(16).mips(4))
		end

		if (index != nil)
			value = "0x0#{(index + baseAddress).to_s(16).upcase}"
			if addressEntry.type == "Thread"
				addressOutput["TH_ADDR_LIST"].push(value)
			elsif addressEntry.type == "EventFlag"
				addressOutput["EV_ADDR_LIST"].push(value)
			elsif addressEntry.type == "Semaphore"
				addressOutput["SEMA_ADDR_LIST"].push(value)
			elsif addressEntry.type == "SceSysMemMemoryBlock"
				addressOutput["GAME_FREEMEM_ADDR"].push(value)
			elsif addressEntry.type == "Vpl"
				addressOutput["VPL_ADDR_LIST"].push(value)
			elsif addressEntry.type == "Fpl"
				addressOutput["FPL_ADDR_LIST"].push(value)
			end
	        else
			puts "can't find address for UID " + addressEntry.uid;
		end
	}

	if (addressOutput["TH_ADDR_LIST"].empty? && addressOutput["EV_ADDR_LIST"].empty? && addressOutput["SEMA_ADDR_LIST"].empty? && addressOutput["GAME_FREEMEM_ADDR"].empty?) then
		puts "!!!! WARNING, NO DATA FOUND FOR THREADS, EVENTS, SEMA, AND GAME ADDRESS. MAKE SURE THAT uidlist.txt IS IN UNIX FORMAT!!!"
	end



	#
	# Look for libraries loaded by the game, for stubs
	#

	uidlist = File.new("uidlist.txt", "r")
	libraryNames = [];

	# [SceModule] 
	# (UID): 0x03E85D71, (entry): 0x881f42e8, (size): 28, (attr): 0x0, (Name): sceATRAC3plus_Library
	# [SceSysMemMemoryBlock]    UID 0x002C9817 (attr 0x0 entry 0x880164c0)

	sceModuleRgx = /\[SceModule\]/
	sceSysMemRgx = /\[SceSysMemMemoryBlock\]/
	regexpLibname = /^\s*(.+)\(Name\): (.+)$/
	scemoduleFound = false;
	while (line = uidlist.gets)
		line.chomp!
		if (line.match(sceModuleRgx))
			scemoduleFound = true;
		end

		if (line.match(sceSysMemRgx))
			scemoduleFound = false;
		end

		if (scemoduleFound && line.match(regexpLibname))
			line.scan(regexpLibname) { |stuff, name|
				libraryNames << name;
			}
		end
	end


	libraryNames.each { |library|
		pos = memdump.index(library);
		if pos then
			pos = pos + 40;
			a = "0x" + memdump[pos+3].to_hex + memdump[pos+2].to_hex + memdump[pos+1].to_hex + memdump[pos].to_hex
			stubLibraries << a.hex
			stubLibrariesNames << library
			#puts  a 
		else
			#puts library + " not found"
		end
	}    
end








puts "stubs found:"
stubLibraries.each { |x|
	puts x.to_hex
}
puts stubLibrariesNames;
puts "***"

# HBL stubs
Config = [
	{
		:lib => "InterruptManager",
		:functions => [
			[0xD61E6961, "sceKernelReleaseSubIntrHandler"],
		],
	},
	{
		:lib => "IoFileMgrForUser",
		:functions => [
			[0x55F4717D, "sceIoChdir"],
			[0x810c4bc3, "sceIoClose"],
			[0xEB092469, "sceIoDclose"],
			[0x54F5FB11, "sceIoDevctl"],
			[0xB29DDF9C, "sceIoDopen"],
			[0xE3EB004C, "sceIoDread"],
			[0x27EB27B8, "sceIoLseek"],
			[0x06A70004, "sceIoMkdir"],
			[0x109f50bc, "sceIoOpen"],
			[0x6A638D83, "sceIoRead"],
			[0x779103A0, "sceIoRename"],
			[0x42ec03ac, "sceIoWrite"],
		],
	},
	{
		:lib => "ModuleMgrForUser",
		:functions => [
			[0x8F2DF740, "ModuleMgrForUser_8F2DF740"], #sceKernelSelfStopUnloadModuleWithStatus, see hook.c
			[0xD8B73127, "sceKernelGetModuleIdByAddress"],
			[0x644395E2, "sceKernelGetModuleIdList"],
			[0x977DE386, "sceKernelLoadModule"],
			[0xD1FF982A, "sceKernelStopModule"],
			[0x2E0911AA, "sceKernelUnloadModule"],
		],
	},
	{
		:lib => "UtilsForUser",
		:functions => [
			# only one of sceKernelDcacheWritebackInvalidateAll and sceKernelDcacheWritebackAll is used, it depends on the exploit,
			[0x79D1C3FA, "sceKernelDcacheWritebackAll"],
			[0xB435DEC5, "sceKernelDcacheWritebackInvalidateAll"],
			[0x34B9FA9E, "sceKernelDcacheWritebackInvalidateRange"],
			[0x3EE30821, "sceKernelDcacheWritebackRange"],
		],
	},
	{
		:lib => "LoadExecForUser",
		:functions => [
			[0x05572A5F, "sceKernelExitGame"],
			[0x4AC57943, "sceKernelRegisterExitCallback"],
		],
	},
	{
		:lib => "SysMemUserForUser",
		:functions => [
			[0x237DBD4F, "sceKernelAllocPartitionMemory"],
			[0xB6D61D02, "sceKernelFreePartitionMemory"],
			[0x9D9A5BA1, "sceKernelGetBlockHeadAddr"],
			# The two following can be overriden by some of our code, which will not fail
			# Our code is slow though, so if syscalls can be done 100% perfect at some point let's put those back
			#[0xA291F107, "sceKernelMaxFreeMemSize"],
			#[0xF919F628, "sceKernelTotalFreeMemSize"],
		],
	},
	{
		:lib => "sceDisplay",
		:functions => [
			[0x289D82FE, "sceDisplaySetFrameBuf"],
			[0x984C27E7, "sceDisplayWaitVblankStart"],
		],
	},
	{
		:lib => "sceCtrl",
		:functions => [
			[0x3A622550, "sceCtrlPeekBufferPositive"],
			[0x1F803938, "sceCtrlReadBufferPositive"],
		],
	},
	{
		:lib => "sceUmdUser",
		:functions => [
			[0xBD2BDE07, "sceUmdUnRegisterUMDCallBack"],
		],
	},
	{
	    	:lib => "sceAudio",
		:functions => [
			[0x6FC46853, "sceAudioChRelease"],
			[0x5EC81C55, "sceAudioChReserve"],
			[0xB011922F, "sceAudioGetChannelRestLength"],
			[0x136CAF51, "sceAudioOutputBlocking"],
			[0x13F592BC, "sceAudioOutputPannedBlocking"],
		],
	},
	{
		:lib => "sceRtc",
		:functions => [
			[0xE7C27D1B, "sceRtcGetCurrentClockLocalTime"],
		],
	},
	{
		:lib => "scePower",
		:functions => [
			[0xEBD177D6, "scePower_EBD177D6"], # == scePowerSetClockFrequency see hook.c
		],
	},
	{
		:lib => "sceUtility",
		:functions => [
			[0x2A2B3DE0, "sceUtilityLoadModule"],
			[0x9A1C91D7, "sceUtilityMsgDialogGetStatus"],
			[0x2AD8E239, "sceUtilityMsgDialogInitStart"],
			[0x67AF3428, "sceUtilityMsgDialogShutdownStart"],
			[0x95FC253B, "sceUtilityMsgDialogUpdate"],
			[0xE49BFE92, "sceUtilityUnloadModule"],
		],
	},
	{
		:lib => "sceGe_user",
		:functions => [
			[0xE47E40E4, "sceGeEdramGetAddr"],
			[0x1F6752AD, "sceGeEdramGetSize"],
		],
	},
	{
		:lib => "sceSuspendForUser",
		:functions => [
			[0x3E0271D3, "sceKernelVolatileMemLock"],
			[0xA569E425, "sceKernelVolatileMemUnlock"], #If sceKernelVolatileMemLock is called twice in one session it will freeze the system, so we need this to unlock volatile memory to prevent that.
		],
	},
	{
		:lib => "ThreadManForUser",
		:functions => [
			[0xE81CAF8F, "sceKernelCreateCallback"],
			[0x55C20A00, "sceKernelCreateEventFlag"],
			[0xD6DA4BA1, "sceKernelCreateSema"],
			[0x446D8DE6, "sceKernelCreateThread"],
			[0xCEADEB47, "sceKernelDelayThread"],
			[0x68DA9E36, "sceKernelDelayThreadCB"],
			[0xEDBA5844, "sceKernelDeleteCallback"],
			[0xEF9E4C70, "sceKernelDeleteEventFlag"],
			[0xED1410E0, "sceKernelDeleteFpl"],
			[0x28B6489C, "sceKernelDeleteSema"],
			[0x9FA03CD3, "sceKernelDeleteThread"],
			[0x89B3D48C, "sceKernelDeleteVpl"],
			[0x809CE29B, "sceKernelExitDeleteThread"],
			[0xAA73C935, "sceKernelExitThread"],
			[0x293B45B8, "sceKernelGetThreadId"],
			[0x75156E8F, "sceKernelResumeThread"],
			[0x876DBFAD, "sceKernelSendMsgPipe"],
			[0x3F53E640, "sceKernelSignalSema"],
			[0x82826F70, "sceKernelSleepThreadCB"],
			[0xF475845D, "sceKernelStartThread"],
			[0x9944F31F, "sceKernelSuspendThread"],
			[0x383F7BCC, "sceKernelTerminateDeleteThread"],
			[0x616403BA, "sceKernelTerminateThread"],
			[0xDF52098F, "sceKernelTryReceiveMsgPipe"],
			[0x884C9F90, "sceKernelTrySendMsgPipe"],
			[0x4E3A1105, "sceKernelWaitSema"],
			[0x6D212BAC, "sceKernelWaitSemaCB"],
		],
	},
]


hooks = Array.new;
nids = Hash.new;
sdk = File.new("sdk.S", "r")

# Matches the following regexp
#	AddNID sceIoLseek, 0x08A885D0
regexpNID = /^\sAddNID (.+), (.+)$/
while (line = sdk.gets)
	line.chomp!
	if (line.match(regexpNID)) 
		line.scan(regexpNID) { |name, addr|
			nids[name] = addr;
		}
	end
end

CantDelete = {
	"sceIoDread" => 1,
	"sceIoDopen" => 1,
	"sceIoDclose" => 1,
	"scePower_EBD177D6" => 1, # TODO handle power functions properly
	"sceUtilityUnloadModule" => 1, #TODO can something be done with sceKernelUnloadModule ?
	"sceUtilityLoadModule" => 1, #TODO can something be done with sceKernelUnloadModule ?
	"sceKernelGetModuleIdByAddress" => 1,
	"sceKernelDeleteVpl" =>  !(addressOutput["VPL_ADDR_LIST"].empty?),
	"sceKernelDeleteFpl" =>  !(addressOutput["FPL_ADDR_LIST"].empty?),
	"sceKernelTerminateThread" => !nids["sceKernelTerminateThread"] || !nids["sceKernelDeleteThread"],
	"sceKernelGetThreadId" => 1,
}
#no Solution for those now :(


def deleteConfig(name)
	puts("Removing #{name}");
	if (CantDelete[name]) then
		puts("HBL needs #{name} so we're Keeping it, but it seems your game does not import it.");
		return false;
	else
		Config.each {|libinfo | 
			libinfo[:functions].delete_if {|x| x[1] == name }
		}
		Config.delete_if{ |x| x[:functions].size == 0 }
		return true;
	end
end

if (addressOutput["VPL_ADDR_LIST"].empty?) then
	deleteConfig("sceKernelDeleteVpl")
end

if (addressOutput["FPL_ADDR_LIST"].empty?) then
	deleteConfig("sceKernelDeleteFpl")
end



#
# Defines a bunch of common hooks
# We also filter out the imports that are not needed. We need to end up with 64 or less of them
#
if (!nids["sceKernelDcacheWritebackInvalidateAll"])
	if (nids["sceKernelDcacheWritebackInvalidateRange"])
		hooks.push(
"// define if your exploit does not have access to sceKernelDcacheWritebackInvalidateAll
#undef CLEAR_CACHE
#define CLEAR_CACHE sceKernelDcacheWritebackInvalidateRange((void*)0x08800000, 0x17FFFFF)
");   
	elsif (nids["sceKernelDcacheWritebackAll"])
		hooks.push(
"// define if your exploit does not have access to sceKernelDcacheWritebackInvalidateAll
#undef CLEAR_CACHE
#define CLEAR_CACHE sceKernelDcacheWritebackAll()
");       
	else
		hooks.push(
"// Seems your exploit does not have access to any cache function
#undef CLEAR_CACHE 
");   
		puts("no function to define CLEAR_CACHE, you might be in trouble here")
	end
end

if (!nids["sceKernelWaitSema"] && nids["sceKernelWaitSemaCB"]) then
	hooks.push(
"// define if your exploit does not have access to sceKernelWaitSema
#undef WAIT_SEMA
#define WAIT_SEMA sceKernelWaitSemaCB
");
end

if (!nids["sceGeEdramGetSize"])
	hooks.push(
"// Define if your exploit does not have access to  sceGeEdramGetSize
// You will see a linker message in the form:
//  undefined reference to `sceGeEdramGetSize'
#define FORCE_HARDCODED_VRAM_SIZE
")
	deleteConfig("sceGeEdramGetSize")
end

if (!nids["sceUtilityCheckNetParam"]) 
	hooks.push(
"// Failing calls to sceUtilityCheckNetParam will probably deactivate net access
#define HOOK_ERROR_sceUtilityCheckNetParam
");
end

if (!nids["sceRtcGetCurrentTick"] && nids["sceRtcGetCurrentClockLocalTime"]) then
	hooks.push(
"#define HOOK_sceRtcGetCurrentTick
"); 
end

hook_if_not_exist = [
	"sceKernelGetThreadId",
	"sceKernelTotalFreeMemSize",
	"sceKernelLoadModule",
];

hook_if_not_exist.each { |v| 
	if (!nids[v]) then
		hooks.push("#define HOOK_#{v}\n");
		deleteConfig(v);
	end
}


a_WITH_b_hooks ={
	"sceAudioGetChannelRestLen" => "sceAudioGetChannelRestLength",
	"sceAudioOutput" => "sceAudioOutputBlocking",
	"sceAudioOutput2GetRestSample" => "sceAudioGetChannelRestLength",
	"sceAudioOutputBlocking" => "sceAudioOutputPannedBlocking",
	"sceAudioOutputPanned" => "sceAudioOutputPannedBlocking",

	"sceCtrlPeekBufferPositive" => "sceCtrlReadBufferPositive",
	"sceCtrlReadBufferPositive" => "sceCtrlPeekBufferPositive",    

	"sceDisplayWaitVblankStartCB" => "sceDisplayWaitVblankStart" ,  

	"sceKernelDcacheWritebackInvalidateAll" => "sceKernelDcacheWritebackInvalidateRange",
	"sceKernelDcacheWritebackInvalidateAll" => "sceKernelDcacheWritebackAll",
	"sceKernelDcacheWritebackAll" => "sceKernelDcacheWritebackRange",     
	"sceKernelReceiveMsgPipe" => "sceKernelTryReceiveMsgPipe",
	"sceKernelSelfStopUnloadModule" => "ModuleMgrForUser_8F2DF740",
	"sceKernelSleepThreadCB" => "sceKernelDelayThreadCB",
	"sceKernelTerminateThread" => "sceKernelTerminateDeleteThread",
	"sceKernelWaitSema" =>  "sceKernelWaitSemaCB",

	"scePowerSetClockFrequency" => "scePower_EBD177D6",

	"sceRtcGetCurrentClock" => "sceRtcGetCurrentClockLocalTime",

	# Dummy: need real functions
	"sceKernelChangeCurrentThreadAttr" => "dummy",
	"sceCtrlSetIdleCancelThreshold"  => "dummy",
	"sceImposeSetHomePopup"   => "dummy",
	"sceKernelReferThreadStatus" => "dummy",
	"sceKernelWakeupThread" => "dummy",
	"sceDisplayGetVcount" => "dummy",
	"sceKernelDeleteSema" => "dummy"
};

delete_b_if_a_exists ={
	"sceKernelDcacheWritebackInvalidateAll" => "sceKernelDcacheWritebackInvalidateRange",
	"sceKernelDcacheWritebackAll" => "sceKernelDcacheWritebackRange",
	"sceKernelWaitSema" =>  "sceKernelWaitSemaCB",
};

a_WITH_b_hooks.each { |k, v| 
	if (!nids[k] && (nids[v] || v == "dummy")) then
		hooks.push("#define HOOK_#{k}_WITH_#{v}\n");
		#deleteConfig(k);
	end
}

delete_b_if_a_exists.each { |k, v| 
	if (nids[k]) then
		deleteConfig(v);
	end
}

if (!nids["sceKernelReleaseSubIntrHandler"]) 
	hooks.push(
"// sceKernelReleaseSubIntrHandler is not imported by the game, so we undef the cleanup function. This might reduce HBL compatibility
#undef SUB_INTR_HANDLER_CLEANUP\n");
end

if (!nids["sceIoChdir"])
	hooks.push(
"// if sceIoCHDIR crashes, don't call it, at all
#define CHDIR_CRASH
");
end

if (!nids["sceKernelUtilsMt19937Init"] || !nids["sceKernelUtilsMt19937UInt"])
	hooks.push("
#define HOOK_mersenne_twister_rdm
");
end

if (nids["sceKernelVolatileMemLock"] && nids["sceKernelVolatileMemUnlock"])
	hooks.push(
"//Prevent homebrew from calling sceKernelVolatileMemLock more than once in one session to prevent the system from freezing.
//sceKernelVolatileMemLock is available, so we can unlock volatile memory after a homebrew locks it.
#define HANDLE_MEMLOCK_MEMUNLOCK
")
elsif (nids["sceKernelVolatileMemLock"] && !nids["sceKernelVolatileMemUnlock"])
	hooks.push(
"//Prevent homebrew from calling sceKernelVolatileMemLock more than once in one session to prevent the system from freezing.
//sceKernelVolatileMemUnlock is not available so we need to hook sceKernelVolatileMemLock to only be called once.
#define HANDLE_MEMLOCK_NO_MEMUNLOCK
")
	deleteConfig("sceKernelVolatileMemUnlock")
else
	deleteConfig("sceKernelVolatileMemLock")
	deleteConfig("sceKernelVolatileMemUnlock")
end

if (nids["sceKernelCreateSema"] && nids["sceKernelDeleteSema"])
	hooks.push(
"//Clean up semaphores created by homebrew
#define SEMAPHORES_CLEANUP
")
else
	deleteConfig("sceKernelCreateSema")
end

if (nids["sceKernelCreateEventFlag"] && nids["sceKernelDeleteEventFlag"] || nids["sceUtilityLoadModule"] && nids["sceUtilityUnloadModule"])
	hooks.push(
"//Clean up event flags created by homebrew
//Comment this define if the game doesn't have sceKernelCreateEventFlag and sceKernelDeleteEventFlag
//and if VHBL doesn't pick them up from the utilities
#define EVENTFLAGS_CLEANUP
")
end

if (nids["sceKernelDcacheWritebackInvalidateAll"] && !nids["sceKernelDcacheWritebackAll"])
	deleteConfig("sceKernelDcacheWritebackAll")
elsif (nids["sceKernelDcacheWritebackAll"] && !nids["sceKernelDcacheWritebackInvalidateAll"])
	deleteConfig("sceKernelDcacheWritebackInvalidateAll")
end

nb_nids = 0;
nids_offset = 0;
Config.each {|libinfo | 
	nb_nids = nb_nids + libinfo[:functions].size
	nids_offset = nids_offset + libinfo[:lib].size + 1 + 3 * 4
}

# Delete functions that are not defined. This will yield compilation error if needed,
# This is the best way to avoid runtime issues if syscall estimation is not working
Config.each {|libinfo |
	libinfo[:functions].each{|function|
		if (!nids[function[1]] && nb_nids > max_nids) then
			if (deleteConfig(function[1])) then
				nb_nids -= 1;
			end
		end
	}
}



# DISABLE_P5_STUBS ?

hooks.push(
"// define DISABLE_P5_STUBS if P5 stubs cause a crash at startup
//#define DISABLE_P5_STUBS
");

# Handle threads in a somewhat magical way
if ((!nids["sceKernelTerminateThread"] || !nids["sceKernelDeleteThread"]) && (!nids["sceKernelTerminateDeleteThread"])) then
	if (nids["sceKernelSuspendThread"] && nids ["sceKernelExitDeleteThread"] && nids["sceKernelResumeThread"])     then
		hooks.push(
"// Alternate solution to handle threads (experimental)
//#define SUSPEND_THEN_DELETE_THREADS
");
	else
		puts("ERROR, No thread handling function, most likely you won't be able to port HBL to this game")
	end
end

net_util = false;
nids.each {|nidname |
	if (nidname[0].start_with?("sceNet")) then
		net_util = true;
		break;
	end
}

if (!net_util) then
	hooks.push(
"// Avoid loading NET, USB and NP utilities if the game doesn't import them (required since firmware 3.00)
#define HOOK_UTILITY
#define AVOID_NET_UTILITY
");
end

nb_nids = 0;
nids_offset = 0;
Config.each {|libinfo |
	nb_nids = nb_nids + libinfo[:functions].size
	nids_offset = nids_offset + libinfo[:lib].size + 1 + 3 * 4
}

if (nb_nids > max_nids) then
	a_WITH_b_hooks.each { |k, v| 
		if (!nids[k] && (nids[v] || v == "dummy") && nb_nids > max_nids) then
			if (deleteConfig(k)) then
				nb_nids -= 1;
			end
		end
	}
end

if (nb_nids > max_nids) then
	puts "TOO MANY NIDS";
	exit;
end

# Write new loader.h
curr_addr = call_address #0x10000; #0x0880FF00 # 0x00013F00
output_folder = "output"
Dir.mkdir(output_folder) unless File.directory?(output_folder);
out_loader_h = File.new( output_folder + "/loader.h", "w");
out_loader_h.binmode;
out_loader_h.puts %Q{/* This file was automatically generated by eLoaderconf.rb */
#ifndef ELOADER_LOADER
#define ELOADER_LOADER

// HBL stubs size
}

out_loader_h.puts "#define NUM_HBL_IMPORTS"  + " 0x"  + nb_nids.to_s(16);
out_loader_h.puts "#define HBL_STUBS_START"  + " 0x"  +  curr_addr.to_s(16);
out_loader_h << "// #define HBL_FUNCTION_NIDS { "

Config.each {|libinfo |
	libinfo[:functions].each {|nidinfo |
		out_loader_h << "0x" + nidinfo[0].to_s(16) + ",";
	}
}

out_loader_h.puts " }"

out_loader_h.puts %Q{
#endif
}

out = File.new( output_folder + "/sdk_hbl.S", "w")
out.binmode();

config_folder = output_folder + "/config";
Dir.mkdir(config_folder) unless File.directory?(config_folder);

out_conf = File.new( config_folder + "/IMPORTS.DAT"  , "w");
puts config_folder + "/IMPORTS.DAT";
out_conf.binmode;
out_conf.write(stubLibraries.size.mips(4));
out_conf.write(Config.size.mips(4));
out_conf.write(nb_nids.mips(4));
stubLibraries.each{ |stub_addr|
	out_conf.write(stub_addr.mips(4));
}

out.puts %Q{# This file was automatically generated by eLoaderconf.rb
# If you want to edit this file, edit eLoaderconf.rb instead
.macro AddNID funcname, offset

    .globl  \\funcname
    .ent    \\funcname
\\funcname = \\offset
#\\funcname:
#	lui $v0, 0x1            # Put 0x10000 in $v0, $v0 is return value so there's no need to back it up
#	lw $v0, 0x18 ($v0)      # Get HBL stubs address, see eloader.c or loader.c for more info
#	addi $v0, $v0, \\offset # Add offset
#	jr $v0                  # Jump to stub
#	nop                     # Leave this delay slot empty!
    .end	\\funcname

.endm

	.file 1 "sdk.c"
	.section .mdebug.eabi32
	.section .gcc_compiled_long32
	.previous
	.text
	.align 2

}


nids_offset_file = nids_offset + 12 + stubLibraries.size * 4;
Config.each {|libinfo |
	lib = libinfo[:lib];
	out_conf.write(lib);
	out_conf.write("\0");
	out_conf.write(libinfo[:functions].size.mips(4));
	out_conf.write(nids_offset_file.mips(8));
	nids_offset_file = nids_offset_file + libinfo[:functions].size * 4
}

Config.each {|libinfo | 
	out.puts;
	out.puts("# " + libinfo[:lib] + "(" + libinfo[:functions].size.to_s + ")");
	max = 0
	libinfo[:functions].each {|nidinfo | 
		if nidinfo[1].size > max then max = nidinfo[1].size end
	}
	libinfo[:functions].each {|nidinfo | 
		out.puts("\tAddNID #{nidinfo[1]}, 0x" +  curr_addr.to_s(16).rjust(4, "0") + " " * (max - nidinfo[1].size) + " # 0x" + nidinfo[0].to_s(16));
		out_conf.write(nidinfo[0].mips(4));
		curr_addr += 8;
	}
}

out.puts %Q{
	.ident "HBL-SDK"
}

out_conf.close
out.close


out = File.new( output_folder + "/exploit_config.h", "w")

out.puts %Q{// This is a pregenerated file made by gen_exploit_config.rb. Some sections are labelled as "TODO", you need to enter your values here

#ifndef EXPLOIT_CONFIG
#define EXPLOIT_CONFIG

//Where to load hbl.bin in Ram
// Has to be a place not occupied by the game
// a correct address can be found in psplink, by typing:
// malloc 2 test l 204800
// (the value you want is head address, try h instead of lto get a different address if needed
// Ideally, it should be either smaller than 0x08900000 or higher then 0x09000000.
// If you don't have any address in these areas, you can use GAME_PRELOAD_FREEMEM, which can free some memory *before* you load HBL, and maybe free some useful ranges
#define HBL_LOAD_ADDRESS TODO

// To get the correct values for TH_ADDR_LIST, EV_ADDR_LIST, SEMA_ADDR_LIST, GAME_FREEMEM_ADDR,
// the best is to use freemem.rb in the tools folder.
// otherwise you can gather these values with psplink

// Addresses where the threads to delete are defined
// can be found with psplink (thlist)
}
out.puts("#define TH_ADDR_LIST { " + addressOutput["TH_ADDR_LIST"].join(", ")  + " }");

out.puts %Q{
//Addresses where the event flags to delete can be found
//psplink evlist
}
if (addressOutput["EV_ADDR_LIST"].size() == 0)
	out << "//"
end
out.puts("#define EV_ADDR_LIST { " + addressOutput["EV_ADDR_LIST"].join(", ")  + " }");

out.puts %Q{
//Addresses where the semaphores to delete can be found
//psplink smlist
}
if (addressOutput["SEMA_ADDR_LIST"].size() == 0)
	out << "//"
end
out.puts("#define SEMA_ADDR_LIST { " + addressOutput["SEMA_ADDR_LIST"].join(", ")  + " }");

if (addressOutput["VPL_ADDR_LIST"].size() == 0)
	out << "//"
end
out.puts("#define VPL_ADDR_LIST { " + addressOutput["VPL_ADDR_LIST"].join(", ")  + " }");

if (addressOutput["FPL_ADDR_LIST"].size() == 0)
	out << "//"
end

out.puts("#define FPL_ADDR_LIST { " + addressOutput["FPL_ADDR_LIST"].join(", ")  + " }");

out.puts %Q{
// Define to Free the game module (e.g. minna no golf)
// Not all games need that
}
if (addressOutput["GAME_FREEMEM_ADDR"].size() == 0)
	out << "//"
end
out.puts("#define GAME_FREEMEM_ADDR { " + addressOutput["GAME_FREEMEM_ADDR"].join(", ")  + " }");

out.puts %Q{
#undef HBL_ROOT
// Replace the "TODO" below with the code of your game (e.g UCUS12345)
#define HBL_ROOT "ms0:/PSP/SAVEDATA/TODO/"

// Some hooks are not really wise if the game already imports them. Files generated by gen_config_exploit try to define hooks
//cleverly based on the game imports, but at runtime additional stubs might be found that make these hooks obsolete.
// This define will prevent some of your hooks from being active, because HBL will decide at runtime that it is not a good idea to do the override
#define DONT_HOOK_IF_FUNCTION_IS_IMPORTED

// Define to free additional modules (net modules, etc...) (e.g. patapon2 demo)
// Not all games need that
#define UNLOAD_ADDITIONAL_MODULES

//There is in general no good reason to comment that one
//It searches for additional syscalls.
#define LOAD_MODULES_FOR_SYSCALLS

// Hooks (function overrides/replacements)
}

out.puts(hooks.join("\n"));

out.puts %Q{
//Vita: all files of HBL in one single folder, no subfolders
#define FLAT_FOLDER
#undef EBOOT_PATH
#define EBOOT_PATH HBL_ROOT"GAME/EBOOT.PBP"
#undef ELF_PATH
#define ELF_PATH HBL_ROOT"GAME/eboot.elf"
#undef HBL_PATH
#define HBL_PATH HBL_ROOT HBL_BIN
#undef LIB_PATH
#define LIB_PATH HBL_ROOT
#undef IMPORTS_PATH
# define IMPORTS_PATH HBL_ROOT


// Some games require especific tricks to work that have to be done either before the loader starts, or before eLoader does anything
//#define PRE_LOADER_EXEC
//int preLoader_Exec();
//#define PRE_HBL_EXEC
//int preHBL_Exec();

#endif

}
